package controllers.similarity;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;

import models.EntityContainer;
import models.EvaluatitionInformation;
import models.ResultPair;
import models.Target;
import controllers.data.RequestBuilder;
import controllers.data.providers.Mql;
import controllers.data.providers.Sparql;
import controllers.data.test.TestData;
import controllers.utils.EntityUtil;


public class EntitySuggestor
{
    enum State {
        correct, found, missing
    };

    public static String sourceEntity = "";
    public static String targetEntity = "";


    @SuppressWarnings ("deprecation")
    public static void main(String[] args)
    {
        List<TestData> testData = readTestData();
        Set<Integer> positions = new HashSet<>();
        do {
            positions.add((int) (Math.random() * testData.size()));
        } while(positions.size() < 100 && positions.size() <= testData.size());

        Target source = Target.FREEBASE, target = null;

        Map<String, Integer> evaluation = new HashMap<>();
        for(State state : State.values()) {
            evaluation.put(state.toString(), 0);
        }
        int z = 0;
        for(Integer i : positions) {
            System.out.print(++z + " ");
            // System.out.println(System.lineSeparator());
            TestData testDataEntry = testData.get(i);
            System.out.println(testDataEntry);
            EntityContainer ec = null;
            switch(source) {
                case DBPEDIA:
                    String dbPediaResource = testDataEntry.dbpedia;
                    // System.out.println(testDataEntry.dbpedia);
                    String localName = EntityUtil.getLocalName(dbPediaResource, false);
                    dbPediaResource = dbPediaResource.replace(localName.replace(" ", "_"), "");
                    dbPediaResource += URLEncoder.encode(localName.replace(" ", "_"));
                    dbPediaResource = dbPediaResource.replace(URLEncoder.encode("("), "(");
                    dbPediaResource = dbPediaResource.replace(URLEncoder.encode(")"), ")");
                    dbPediaResource = dbPediaResource.replace(URLEncoder.encode(","), ",");
                    // System.out.println(dbPediaResource);
                    ec = Sparql.loadInstance(dbPediaResource, Target.DBPEDIA);
                    ec.setValidationLink(testDataEntry.freebase);
                    target = Target.FREEBASE;
                break;
                case FREEBASE:
                    ec = Mql.loadInstance(testDataEntry.freebase, Target.FREEBASE);
                    ec.setValidationLink(testDataEntry.dbpedia);
                    target = Target.DBPEDIA;
                break;
                case YAGO: // ignore this case
                break;
                default:
                break;
            }

            sourceEntity = ec.getURI();
            targetEntity = ec.getValidationLink();

            Set<ResultPair> result = getSuggestion(ec, source, target);
            State cs = evaluateResult(ec, result, testDataEntry);
            int count = evaluation.get(cs.toString());
            evaluation.put(cs.toString(), ++count);
        }

        System.out.println(System.lineSeparator() + System.lineSeparator() + "Evaluation:");
        for(String key : evaluation.keySet()) {
            System.out.println(String.format("%-15s", key) + " ->\t" + evaluation.get(key));
        }
    }


    @SuppressWarnings ("deprecation")
    public static State evaluateResult(EntityContainer ec, Set<ResultPair> result, TestData testDataEntry)
    {
        // System.out.println(ec.getLocalName() + " - " + ec.getURI());
        String validationLink = URLDecoder.decode(ec.getValidationLink());
        System.out.println(ec.getURI() + "\n\tcorrect link: " + validationLink);
        // evaluation
        State state = State.missing;
        double maxValue = 0;
        for(ResultPair rp : new TreeSet<>(result).descendingSet()) {
            String suggestionLink = URLDecoder.decode(rp.getUrl());
            System.out.print(System.lineSeparator() + String.format("%-80s", suggestionLink) + "\t" + String.format("%1.5f", rp.getConfidence()));
            if(suggestionLink.equals(validationLink)) {
                if(rp.getConfidence() < maxValue) {
                    state = State.found;
                    System.out.print("\t\t" + state);
                } else {
                    state = State.correct;
                    System.out.print("\t\t" + state);
                }
            }
            maxValue = Math.max(maxValue, rp.getConfidence());
        }
        System.out.print(System.lineSeparator() + "\t");
        System.out.println("State: \t" + state + " (suggestions: " + result.size() + ")");
        // if(state == State.missing) {
        // testDataEntry.appendToFile();
        // }
        return state;
    }


    @SuppressWarnings ("deprecation")
    private static State evaluatePartialResult(String target, NavigableSet<EntitySimilarity> matchingSet)
    {
        String validationLink = URLDecoder.decode(target);
        State state = State.missing;
        float maxValue = 0;
        for(EntitySimilarity es : matchingSet) {
            String url = URLDecoder.decode(es.getMatchingCanditate().getEvaluationID());
            float conf = es.getConfidenceWithBonus();
            if(url.equals(validationLink)) {
                if(conf < maxValue) {
                    return State.found;
                } else {
                    return State.correct;
                }
            }
            maxValue = Math.max(maxValue, conf);
        }
        return state;
    }


    private static int compareState(State oldState, State newState)
    {
        if((oldState != State.correct && newState == State.correct) || (oldState == State.missing && newState == State.found)) {
            return 1;
        } else if(oldState == newState) {
            return 0;
        } else {
            return -1;
        }
    }


    public static List<TestData> readTestData()
    {
        List<TestData> testData = new ArrayList<>();

        BufferedReader inputStream = null;

        try {
            inputStream = new BufferedReader(new FileReader("data/person_sameas.csv")); // person_sameas.csv

            String entry;
            String[] data;
            while((entry = inputStream.readLine()) != null) {
                data = entry.split(";");
                if(data.length > 1) {
                    TestData pair = new TestData(data[0], data[1]);
                    testData.add(pair);
                }
            }
            inputStream.close();
        } catch(IOException e) {
            e.printStackTrace();
        }
        return testData;
    }


    public static Set<ResultPair> getSuggestion(EntityContainer ec, Target source, Target target)
    {
        Map<String, EntityContainer> suggestions = new HashMap<>();

        for(String keyword : ec.getKeywords()) {
            for(EntityContainer found : RequestBuilder.search(keyword, target)) {
                if(!suggestions.containsKey(found.getEvaluationID()) || (suggestions.containsKey(found.getEvaluationID()) && suggestions.get(found.getEvaluationID()).getConfidence() < found.getConfidence())) {
                    suggestions.put(found.getEvaluationID(), found);
                }
            }
        }

        int count = 0;
        NavigableSet<EntityContainer> s = new TreeSet<>(suggestions.values()).descendingSet();

        Set<EntitySimilarity> tempResult = new TreeSet<>();
        for(EntityContainer ecSuggestion : s) {
            ecSuggestion = loadInstance(ecSuggestion.getURI(), ecSuggestion.getSource());

            EntitySimilarity entitySimilarity = new EntitySimilarity(ec, ecSuggestion);
            entitySimilarity.computeConfidence();
            float confidence = entitySimilarity.getConfidence();
            ecSuggestion.setConfidence(confidence);
            if(confidence > 0)
                tempResult.add(entitySimilarity);
            if(count++ == 20)
                break;
        }

        EvaluatitionInformation ei = new EvaluatitionInformation();
        ei.source = source.toString();
        ei.target = target.toString();
        ei.sourceEntity = sourceEntity;
        ei.targetEntity = targetEntity;
        ei.mcBeforeReducing = tempResult.size();

        State s1 = evaluatePartialResult(targetEntity, new TreeSet<>(tempResult).descendingSet());

        // System.out.println("EI: before reducing: " + tempResult.size());

        float startValue = 0, endValue = 1, maxDiff = 0.3f;
        for(EntitySimilarity rp : new TreeSet<>(tempResult).descendingSet()) {
            startValue = (float) Math.max(startValue, rp.getConfidence());
            if(startValue - rp.getConfidence() > maxDiff) {
                tempResult.remove(rp);
            } else {
                endValue = (float) Math.min(endValue, rp.getConfidence());
            }
        }

        ei.mcAfterReducing = tempResult.size();
        // System.out.println("EI: after reducing: " + tempResult.size());

        State s2 = evaluatePartialResult(targetEntity, new TreeSet<>(tempResult).descendingSet());
        ei.change1 = compareState(s1, s2);

        // System.out.println("EI: state1: " + s1 + " - " + s2 + " / " + compareState(s1, s2));

        if(tempResult.size() > 1) {// && startValue - endValue < 0.3) {
            int sum = 0;
            for(EntitySimilarity es : tempResult) {
                sum += es.getMatchedPropertiesNumber();
            }
            ei.prematchedProperties = sum;

            // System.out.println("EI-graph prematched prop: " + sum);

            for(EntitySimilarity es : tempResult) {
                GraphLoader gl1 = new GraphLoader(es.getStartInstance(), source);
                GraphLoader gl2 = new GraphLoader(es.getMatchingCanditate(), target);
                for(PropertyComparison pc : es.getSimilarProperties()) {
                    EntityContainer el1 = gl1.load(pc.getP1());
                    EntityContainer el2 = gl2.load(pc.getP2());
                    if(el1 != null && el2 != null) {
                        ei.comparableProperties += 1;
                        EntitySimilarity esTemp = new EntitySimilarity(el1, el2);
                        esTemp.computeConfidence();
                        ei.propertyConf.add(esTemp.getConfidence());
                        if(esTemp.getConfidence() > 0) {
                            es.addBonus(esTemp.getConfidence());
                        }
                        // System.out.println("EI-graph (" + ei.comparableProperties + "): " + esTemp.getConfidence() + " / " + ei.propertyConf.size());
                    } else {
                        // System.out.println("EI-graph: NULL");
                    }
                }
            }
        }

        State s3 = evaluatePartialResult(targetEntity, new TreeSet<>(tempResult).descendingSet());
        ei.change2 = compareState(s2, s3);
        ei.result = s3.toString();

        // System.out.println("EI: state2: " + s1 + " - " + s3 + " / " + compareState(s2, s3));

        ei.saveToFile();

        Set<ResultPair> result = new HashSet<>();
        for(EntitySimilarity es : tempResult) {
            ResultPair currentResult = new ResultPair(es.getMatchingCanditate().getEvaluationID(), es.getConfidenceWithBonus(), es.getMatchingCanditate().getHTMLLink());
            result.add(currentResult);
        }

        return result;
    }


    private static EntityContainer loadInstance(String url, Target source)
    {
        EntityContainer instance;
        switch(source) {
            case DBPEDIA:
            case YAGO:
                instance = Sparql.loadInstance(url, source);
            break;
            case FREEBASE:
                instance = Mql.loadInstance(url, source);
            break;
            default:
                System.err.println("Not yet implemented provider");
                return null;
        }
        return instance;
    }
}